﻿using System;
using SoftwareConsulting.BI11.Studio.InternalServices;
using SoftwareConsulting.BI11.NumericalMethods.MatrixAlgebra;
using SoftwareConsulting.BI11.NumericalMethods.DhbFunctionEvaluation;

namespace SoftwareConsulting.BI11.NumericalMethods.Regression
{
    /// <summary>
    /// Класс линейной регрессии
    /// </summary>
    public class LinearRegression
    {        
        private int _accumulatedPointsCount;//число хранимых точек        
        private double _sumX;        
        private double _sumY;        
        private double _sumXX;        
        private double _sumXY;        
        private double _sumYY;        
        private double _slope;//уклон        
        private double _intercept;//отрезок прямой        
        private double _correlationCoefficient;//коэффициент корреляции

        /// <summary>
        /// Конструктор класса
        /// </summary>
        public LinearRegression()
        {
            Reset();
        }

        /// <summary>
        /// Добавляет новую точку
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void Add(double x, double y)
        {
            Add(x, y, 1);
        }

        /// <summary>
        /// Добавляет новую точку
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="w"></param>
        public void Add(double x, double y, double w)
        {
            double wx = w * x;
            double wy = w * y;
            _accumulatedPointsCount += (int)w;
            _sumX += wx;
            _sumY += wy;
            _sumXX += wx * x;
            _sumYY += wy * y;
            _sumXY += wx * y;
            ResetResults();
        }

        /// <summary>
        /// Возвращает как полиномиальную функцию с оценкой ошибок
        /// </summary>
        /// <returns></returns>
        public EstimatedPolynomial AsEstimatedPolynomial()
        {
            return new EstimatedPolynomial(this.Coefficients, GetErrorsMatrix());
        }

        /// <summary>
        /// Возвращает как полиномиальную функцию
        /// </summary>
        /// <returns></returns>
        public PolynomialFunction AsPolynomial()
        {
            return new PolynomialFunction(this.Coefficients);
        }

        /// <summary>
        /// Возвращает полиномиальные коэффициенты
        /// </summary>
        private double[] Coefficients
        {
            get 
            { 
                return new double[] { this.Intercept, this.Slope }; 
            }
        }

        /// <summary>
        /// Производит расчет
        /// </summary>
        private void Calculate()
        {
            double xNorm = _sumXX * _accumulatedPointsCount - _sumX * _sumX;
            double xyNorm = _sumXY * _accumulatedPointsCount - _sumX * _sumY;
            _slope = xyNorm / xNorm;
            _intercept = (_sumXX * _sumY - _sumXY * _sumX) / xNorm;
            _correlationCoefficient = xyNorm / Math.Sqrt(xNorm * (_sumYY * _accumulatedPointsCount - _sumY * _sumY));
        }

        /// <summary>
        /// Возвращает матрицу ошибок
        /// </summary>
        /// <returns></returns>
        public SymmetricMatrix GetErrorsMatrix()
        {
            double[,] rows = new double[2, 2];
            rows[1, 1] = 1.0 / (_sumXX * _accumulatedPointsCount - _sumX * _sumX);
            rows[0, 1] = _sumXX * rows[1, 1];
            rows[1, 0] = rows[0, 1];
            rows[0, 0] = _sumXX * rows[1, 1];
            SymmetricMatrix result = null;
            try
            {
                try 
                { 
                    result = SymmetricMatrix.FromComponents(rows); 
                }
                catch (BIMatrixIllegalDimensionException ex) 
                {
                    Trace.StartBlock("BIMatrixIllegalDimensionException killed");
                    Trace.Message("BIMatrixIllegalDimensionException catched and silently killed! Exception text:");
                    Trace.Message(ex.ToString());
                    Trace.EndBlock();
                };                
            }
            catch (BIMatrixNonSymmetricComponentsException ex) 
            {
                Trace.StartBlock("BIMatrixNonSymmetricComponentsException killed");
                Trace.Message("BIMatrixNonSymmetricComponentsException catched and silently killed! Exception text:");
                Trace.Message(ex.ToString());
                Trace.EndBlock();
            };
            return result;
        }

        /// <summary>
        /// Возвращает коэффициент корреляции
        /// </summary>
        public double CorrelationCoefficient
        {
            get
            {
                if (double.IsNaN(_correlationCoefficient))
                    Calculate();
                return _correlationCoefficient;
            }
        }

        /// <summary>
        /// Возвращает отрезок прямой
        /// </summary>
        public double Intercept
        {
            get
            {
                if (double.IsNaN(_intercept))
                    Calculate();
                return _intercept;
            }
        }

        /// <summary>
        /// Возвращает уклон
        /// </summary>
        public double Slope
        {
            get
            {
                if (double.IsNaN(_slope))
                    Calculate();
                return _slope;
            }
        }

        /// <summary>
        /// Удаляет точку
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void Remove(double x, double y)
        {
            _accumulatedPointsCount -= 1;
            _sumX -= x;
            _sumY -= y;
            _sumXX -= x * x;
            _sumYY -= y * y;
            _sumXY -= x * y;
            ResetResults();
        }

        /// <summary>
        /// Сбрасывает все
        /// </summary>
        public void Reset()
        {
            _accumulatedPointsCount = 0;
            _sumX = 0;
            _sumY = 0;
            _sumXX = 0;
            _sumYY = 0;
            _sumXY = 0;
            ResetResults();
        }

        /// <summary>
        /// Сбрасывает результаты расчетов
        /// </summary>
        private void ResetResults()
        {
            _slope = double.NaN;
            _intercept = double.NaN;
            _correlationCoefficient = double.NaN;
        }

        /// <summary>
        /// Возвращает значение в указанной точке
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public double Value(double x)
        {
            return x * this.Slope + this.Intercept;
        }
    }
}
